#!/usr/bin/perl
#
# Copyright 2008-2011 by Brian Dominy <brian@oddchange.com>
#
# This file is part of FreeWPC.
#
# FreeWPC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# FreeWPC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FreeWPC; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# ctemp - program for compiling a C template (".ct") file into
#   multiple C/H files.
#
# TODO:
# - method to declare maximum number of instantiations


use Getopt::Long;

my $class = "noclass";
my $ofile = undef;
my $infile = undef;
my $outdir = ".";
my $verbose = 0;
my %macrotab;


# Define a new variable.
sub define {
	my ($var, $value) = @_;
	if (defined ($macrotab{$var}) && $macrotab{$var} ne $value) {
		print "Warning: redefining '$var'\n";
	}
	print ">>> $var = $value\n" if ($verbose);
	$macrotab{$var} = $value;
}


# Assert that a variable must be defined.
# Fail now if it is not.
sub need {
	my $var = shift;
	die "Undefined variable '$var'" if (!defined $macrotab{$var});
}


# Expand a string that may contain macros.
sub expand {
	my $string = shift;
	for my $var (keys %macrotab) {
		my $value = $macrotab{$var};
		$string =~ s/\@$var/$value/g;
	}
	return $string;
}


# Change the current output file.
sub chfile {
	my ($file) = @_;
	if ($file ne $ofile) {
		if ($ofile) {
			print "(ctemp): Closed '$ofile'\n" if ($verbose);
			close OFH;
		}
		if ($file) {
			my $C = $macrotab{"class"};
			my $SC = $macrotab{"superclass"};
			if ($SC) { $C .= "+" . $SC; }

			open OFH, ">$outdir/$file" or die "$!";
			print "(ctemp): Opened '$file'\n" if ($verbose);

			if ($file =~ /\.[ch]$/) {
				print OFH "\n/* This file was autogenerated by 'ctemp' from $infile for class $C. */\n\n";
			} else {
				print OFH "\n# This file was autogenerated by 'ctemp' from $infile for class $C.\n\n";
			}
		}
	}
	$ofile = $file;
}


# Write a line to the current output file.
sub output {
	my $line = shift;
	print OFH "$line\n" if ($ofile);
}


# Parse an input template file.
sub parse {
	my $infile = shift;
	print "(ctemp): reading template '$infile'\n" if ($verbose);
	open FH, "$infile" or die "$!";
	while (<FH>) {
		# Remove newlines and skip blank lines.
		chomp;
		next if (/^\@\@$/);

		# Handle @@ directives.

		# @@classvar <name>
		# ----------------------------------------------------
		if (/^\@\@classvar (.*)$/) {
			if ($macrotab{'instance'} == 0) {
				$_ = $1;
			}
			else {
				next;
			}
		}

		# @@class <name>
		# ----------------------------------------------------
		if (/^\@\@class ([^ ]*)/) {
			if (!defined $macrotab{"class"}) {
				$class = $1;
				define ("class", $class);
				if (!defined ($macrotab{'self'})) {
					define ("self", expand ("$class\@instance"));
				}
				define ("superclass", "");
			}
			else {
				$macrotab{"superclass"} .= " " . $1;
			}
		}


		# @@parameter <name>
		# Declare that the template takes a parameter NAME.
		# It should already have a value.
		# ----------------------------------------------------
		elsif (/^\@\@parameter ([a-z]*)$/) {
			need $1;
		}

		# @@file <name>
		# Change the current output file to NAME.
		# ----------------------------------------------------
		elsif (/^\@\@file ([^ ]*)/) {
			chfile (expand ($1));
		}

		# @@define <name> <value>
		# ----------------------------------------------------
		elsif (/^\@\@define ([a-z]*) (.*)$/) {
			define ($1, $2);
		}

		# @@weakdefine <name> <value>
		# A weakdefine provides a default value, but won't
		# clobber one that already exists.
		# ----------------------------------------------------
		elsif (/^\@\@weakdefine ([a-z]*) (.*)$/) {
			if (!defined $macrotab{$1}) {
				define ($1, $2);
			}
		}

		# @@include <name>
		# ----------------------------------------------------
		elsif (/^\@\@include ([^ ]*)/) {
			parse ($1);
		}

		# Anything else is output as-is.
		else {
			output (expand ($_));
		}
	}
	close FH;
}


#
# Main program.
#
GetOptions (
	"D=s" => sub {
		my ($var, $value) = split /=/, $_[1];
		define ($var, $value);
	},
	"i=s" => sub { define ("instance", $_[1]); },
	"s=s" => sub { define ("self", $_[1]); define ("SELF", uc($_[1])); },
	"o=s" => \$outdir
) or die "Invalid options";

parse ($ARGV[0]);
chfile (undef);
